package com.rustic.server.controller.api;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.json.JSONUtil;
import cn.xy.base.server.service.SysConfigService;
import cn.xy.base.server.vo.LoginResult;
import cn.xy.commons.annotation.ApiAuth;
import cn.xy.commons.cache.CacheManager;
import cn.xy.commons.constant.Consts;
import cn.xy.commons.core.Result;
import cn.xy.commons.enums.YesNoStatus;
import cn.xy.commons.exception.JzRuntimeException;
import cn.xy.commons.web.ResponseUtil;
import com.rustic.server.bean.WeixinConfig;
import com.rustic.server.dto.weixin.WeiXinLoginDTO;
import com.rustic.server.dto.weixin.WeiXinMiniProgramUserInfoDTO;
import com.rustic.server.enums.UserWeixinType;
import com.rustic.server.po.User;
import com.rustic.server.po.UserWeixin;
import com.rustic.server.service.UserService;
import com.rustic.server.service.UserWeixinService;
import com.rustic.server.utils.WxConfiguration;
import com.rustic.server.vo.user.UserSimpleVO;
import com.rustic.server.vo.weixin.WeixinOfficeUserTokenVO;
import com.rustic.server.vo.weixin.WxLoginResult;
import com.rustic.server.web.WebContext;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.WxJsapiSignature;
import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.service.WxOAuth2Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.net.URLEncoder;
import java.util.List;

/**
 * @author jianzheng
 */
@Slf4j
@Api(tags = {"微信回调"})
@RestController
@RequestMapping("/api/weixin")
@ApiAuth(ignore = true)
public class WeixinController {

    @Autowired
    private SysConfigService sysConfigService;

    @Autowired
    private UserService userService;

    @Autowired
    private UserWeixinService userWeixinService;

    @Autowired
    private CacheManager cacheManager;

    private static final String ERR_MSG = "微信授权失败!";

    private static final String CACHE_KEY = "WX_LOGIN_OPEN_ID_";

    private static final String ERROR = "error";

    private static final String SUCCESS = "success";

    private static final String ASYNC_MESSAGE_TOKEN = "这是微信后台配置的异步通知token";

    @ApiOperation(value = "小程序openId获取")
    @PostMapping("/mGetOpenId")
    public Result<String> mGetOpenId(@RequestBody @Validated WeiXinLoginDTO weiXinLoginDTO) {
        WeixinConfig weixinConfig = sysConfigService.getWeixinConfig(SysConfigService.miniprogramConfigKey);
        final WxMaService wxService = WxConfiguration.getMaService(weixinConfig);
        WxMaJscode2SessionResult session;
        try {
            session = wxService.getUserService().getSessionInfo(weiXinLoginDTO.getCode());
            cacheManager.set(CACHE_KEY + session.getOpenid(), session, 30 * 60);
            log.info("weixinMiniProgramUserInfo=>{}", session);
        } catch (WxErrorException e) {
            log.error("weixinConfig=>{}", weixinConfig);
            return Result.error(ERR_MSG);
        }
        if (CharSequenceUtil.isNotEmpty(session.getOpenid())) {
            return Result.data(session.getOpenid());
        }
        return Result.error(ERR_MSG);
    }

    @ApiOperation(value = "小程序登录")
    @PostMapping("/mLogin")
    public Result<WxLoginResult<UserSimpleVO>> mLogin(@RequestBody @Validated WeiXinLoginDTO weiXinLoginDTO) {
        WeixinConfig weixinConfig = sysConfigService.getWeixinConfig(SysConfigService.miniprogramConfigKey);
        final WxMaService wxService = WxConfiguration.getMaService(weixinConfig);
        WxMaJscode2SessionResult session;
        try {
            session = wxService.getUserService().getSessionInfo(weiXinLoginDTO.getCode());
            cacheManager.set(CACHE_KEY + session.getOpenid(), session, 30 * 60);
            log.debug("weixinMiniProgramUserInfo=>{}", session);
        } catch (WxErrorException e) {
            log.debug("weixinConfig=>{}", weixinConfig);
            return Result.error(ERR_MSG);
        }

        UserWeixin userWeixin;
        if (CharSequenceUtil.isNotEmpty(session.getUnionid())) {
            userWeixin = userWeixinService.getByUnionId(session.getUnionid());
        } else {
            userWeixin = userWeixinService.getByOpenId(weixinConfig.getAppId(), session.getOpenid());
        }
        if (userWeixin != null) {
            User user = userService.getById(userWeixin.getUserId());
            if (user == null) {
                return Result.error(Consts.USER_NOT_EXIST, "您的账号授权异常，请重新授权");
            }
            if (YesNoStatus.isYes(user.getIsLock())) {
                return Result.error(Consts.USER_BANNED, "您的账号数据异常");
            }
            UserWeixin checkSave = userWeixinService.getByOpenId(weixinConfig.getAppId(), session.getOpenid());
            if (checkSave == null) {
                userWeixinService.add(user.getId(), UserWeixinType.MINI_PROGRAM, session.getUnionid(), session.getOpenid(), weixinConfig.getAppId());
            }
            // 生成jwt
            String ip = WebContext.getIp();
            String token = userService.login(ip, user);
            UserSimpleVO userSimpleVO = userService.getSimpleById(user.getId());
            return Result.data(new WxLoginResult<>(token, userSimpleVO, session.getOpenid()));
        }

        if (CharSequenceUtil.isNotEmpty(session.getOpenid())) {
            return Result.data(new WxLoginResult<>(session.getOpenid()));
        }
        return Result.error(ERR_MSG);
    }

    @ApiOperation(value = "获取小程序用户信息")
    @PostMapping("/mGetUserInfo")
    public Result<LoginResult<UserSimpleVO>> mGetUserInfo(@RequestBody @Validated WeiXinMiniProgramUserInfoDTO dto) {
        WeixinConfig weixinConfig = sysConfigService.getWeixinConfig(SysConfigService.miniprogramConfigKey);
        final WxMaService wxService = WxConfiguration.getMaService(weixinConfig);
        // 从缓存中获取静默授权信息
        WxMaJscode2SessionResult session = cacheManager.get(CACHE_KEY + dto.getOpenid());
        if (session == null) {
            return Result.error("微信授权信息已过期，请重新授权！");
        }
        // 用户信息校验
        if (!wxService.getUserService().checkUserInfo(session.getSessionKey(), dto.getRawData(), dto.getSignature())) {
            return Result.error("微信用户信息解析失败！");
        }
        // 解密用户信息
        WxMaUserInfo userInfo = wxService.getUserService().getUserInfo(session.getSessionKey(), dto.getEncryptedData(), dto.getIv());
        if (userInfo == null) {
            return Result.error("微信用户信息获取失败！");
        }
        if (CharSequenceUtil.isNotEmpty(session.getUnionid())) {
            userInfo.setUnionId(session.getUnionid());
        }
        log.debug("WxMaUserInfo=>{}", userInfo);

        UserWeixin userWeixin;
        if (CharSequenceUtil.isNotEmpty(userInfo.getUnionId())) {
            userWeixin = userWeixinService.getByUnionId(userInfo.getUnionId());
        } else {
            userWeixin = userWeixinService.getByOpenId(weixinConfig.getAppId(), dto.getOpenid());
        }

        User user;
        String ip = WebContext.getIp();
        if (userWeixin == null) {
            user = userService.createByWxMaUserInfo(weixinConfig.getAppId(), userInfo, dto.getOpenid(), ip);
        } else {
            user = userService.getById(userWeixin.getUserId());
            if (user == null) {
                return Result.error("微信用户被删除");
            }
        }

        // 生成jwt
        String token = userService.login(ip, user);
        UserSimpleVO userSimpleVO = userService.getSimpleById(user.getId());
        return Result.data(new LoginResult<>(token, userSimpleVO));
    }

//    @ApiOperation(value = "小程序手机号授权")
//    @PostMapping("/mGetMobile")
//    public Result<String> mGetMobile(@RequestBody @Validated WeiXinMiniProgramMobileDTO dto) {
//        WeixinConfig weixinConfig = sysConfigService.getWeixinConfig(SysConfigService.miniprogramConfigKey);
//        final WxMaService wxService = WxConfiguration.getMaService(weixinConfig);
//        // 解密
//        WxMaPhoneNumberInfo phoneNoInfo = WxConfiguration.getPhoneNoInfo(wxService, dto.getCode());
//        if (phoneNoInfo == null) {
//            return Result.error("微信手机信息获取失败！");
//        }
//        User user = userService.getByMobile(phoneNoInfo.getPhoneNumber());
//        if (user != null) {
//            return Result.error("该手机号已被其他账号绑定");
//        }
//        userService.updateMobile(WebContext.getUserId(), phoneNoInfo.getPhoneNumber(), YesNoStatus.YES);
//        return Result.ok();
//    }

    @ApiOperation(value = "生成小程序码")
    @GetMapping("/mGetWxQrCode")
    @ApiAuth(ignore = false)
    public void mGetWxQrCode(@ApiParam(value = "跳转页面路径（env=release时，需要保证页面必须在小程序内存在）") String path,
                             @ApiParam(value = "参数（服务端会对参数自动做URLEncode处理，但需要保证参数URLEncode后不能超过32字符）") String param,
                             @ApiParam(value = "正式版为：release，体验版为：trial，开发版为：develop") String env,
                             HttpServletResponse response) {
        List<String> envList = CollUtil.toList("release", "trial", "develop");
        if (!envList.contains(env)) {
            throw new JzRuntimeException("请选择正确的环境参数");
        }
        WeixinConfig weixinConfig = sysConfigService.getWeixinConfig(SysConfigService.miniprogramConfigKey);
        final WxMaService wxService = WxConfiguration.getMaService(weixinConfig);
        try {
            param = URLEncoder.encode(param, "utf-8");
            byte[] imageByte = wxService.getQrcodeService()
                    .createWxaCodeUnlimitBytes(param, path, false, env, 430, true, null, true);
            BufferedImage image = ImgUtil.toImage(imageByte);
            ResponseUtil.renderPng(response, image);
        } catch (Exception e) {
            log.error("生成小程序码生成异常：", e);
            throw new JzRuntimeException("图片生成错误");
        }
    }

    @ApiOperation(value = "公众号jsCode2UserInfo")
    @GetMapping(value = "jsCode2UserInfo")
    public Result<WeixinOfficeUserTokenVO> jsCode2UserInfo(String code) {
        WeixinConfig weixinConfig = sysConfigService.getWeixinConfig(SysConfigService.weixinOfficeConfigKey);
        final WxOAuth2Service wxOauth2Service = WxConfiguration.getOAuth2Service(weixinConfig);

        WxOAuth2AccessToken wxOauth2AccessToken;
        try {
            wxOauth2AccessToken = wxOauth2Service.getAccessToken(code);
        } catch (WxErrorException e) {
            log.error("微信授权失败: ", e);
            return Result.error(ERR_MSG);
        }
        return Result.data(new WeixinOfficeUserTokenVO(wxOauth2AccessToken));
    }

    @ApiOperation(value = "公众号h5登录")
    @PostMapping("/h5Login")
    public Result<LoginResult<UserSimpleVO>> h5Login(@RequestBody @Validated WeiXinLoginDTO weiXinLoginDTO) {
        WeixinConfig weixinConfig = sysConfigService.getWeixinConfig(SysConfigService.weixinOfficeConfigKey);
        final WxOAuth2Service service = WxConfiguration.getOAuth2Service(weixinConfig);

        WxOAuth2UserInfo wxOauth2UserInfo;
        try {
            WxOAuth2AccessToken wxOauth2AccessToken = service.getAccessToken(weiXinLoginDTO.getCode());
            wxOauth2UserInfo = service.getUserInfo(wxOauth2AccessToken, null);
        } catch (Exception e) {
            log.error("微信授权失败: ", e);
            return Result.error(ERR_MSG);
        }
        log.info("wxOauth2UserInfo=>{}", wxOauth2UserInfo);

        String ip = WebContext.getIp();
        UserWeixin userWeixin;
        if (CharSequenceUtil.isNotEmpty(wxOauth2UserInfo.getUnionId())) {
            userWeixin = userWeixinService.getByUnionId(wxOauth2UserInfo.getUnionId());
        } else {
            log.info("AppId&Openid=>{} ===> {}", weixinConfig.getAppId(), wxOauth2UserInfo.getOpenid());
            userWeixin = userWeixinService.getByOpenId(weixinConfig.getAppId(), wxOauth2UserInfo.getOpenid());
            log.info("userWeixin ===> {}", userWeixin);
        }
        User user = null;
        if (userWeixin == null) {
            user = userService.createByWxOauth2UserInfo(weixinConfig.getAppId(), wxOauth2UserInfo, ip);
        } else {
            user = userService.getById(userWeixin.getUserId());
            if (user == null) {
                return Result.error("微信用户被删除");
            }
        }

        // 生成jwt
        String token = userService.login(ip, user);
        UserSimpleVO userSimpleVO = userService.getSimpleById(user.getId());
        return Result.data(new LoginResult<>(token, userSimpleVO));
    }

    @ApiOperation(value = "公众号分享")
    @GetMapping("/jsShareConfig")
    public Result<WxJsapiSignature> jsShareConfig(@RequestParam String url) {
        WeixinConfig weixinConfig = sysConfigService.getWeixinConfig(SysConfigService.weixinOfficeConfigKey);
        final WxMaService wxService = WxConfiguration.getMaService(weixinConfig);

        WxJsapiSignature wxJsapiSignature;
        try {
            wxJsapiSignature = wxService.getJsapiService().createJsapiSignature(url);
        } catch (Exception e) {
            log.error("微信分享配置获取失败: ", e);
            return Result.error("微信分享配置获取失败！");
        }
        return Result.data(wxJsapiSignature);
    }

    /**
     * 微信异步消息推送服务器验证
     */
    @GetMapping("/asyncMessage")
    @ApiOperation(value = "微信异步消息推送服务器验证(服务器验证)")
    public void asyncMessage(String signature, String nonce, String timestamp, String echostr, HttpServletResponse response) {
        boolean result = WxConfiguration.checkSignature(signature, timestamp, nonce, ASYNC_MESSAGE_TOKEN);
        if (!result) {
            ResponseUtil.renderText(response, ERROR);
            return;
        }
        ResponseUtil.renderText(response, echostr);
    }

    /**
     * 微信异步消息推送
     */
    @PostMapping("/asyncMessage")
    @ApiOperation(value = "微信异步消息推送")
    public void asyncMessage(String signature, String nonce, String timestamp, @RequestBody Object json, HttpServletResponse response) {
        boolean result = WxConfiguration.checkSignature(signature, timestamp, nonce, ASYNC_MESSAGE_TOKEN);
        if (!result) {
            ResponseUtil.renderText(response, ERROR);
            return;
        }
        String jsonStr = JSONUtil.toJsonStr(json);
        log.info("asyncMessage result jsonStr = {}", jsonStr);
        ResponseUtil.renderText(response, SUCCESS);
    }

}
